#pragma warning(disable : 4355)
#include "Avalon/Codecs/CodedReader.hpp"
#include <boost/bind.hpp>
#include "Avalon/Codecs/Decoder.hpp"
#include "Avalon/IO/BufferReader.hpp"
#include "Avalon/IO/IOException.hpp"

using namespace Avalon;
using namespace Avalon::Codecs;
using namespace Avalon::IO;
using namespace Avalon::Threading;
using namespace boost;
using namespace std;

CodedReader::CodedReader(Carry<Reader> source, Decoder* decoder)
    : m_source(source),
      m_decoder(decoder),
      m_sizeRemaining(0) {
  Async<int>::State readResult;
  m_bufferRead.SetFinishedSlot(bind(&CodedReader::OnBufferRead, this),
    Store(readResult));
  m_dataRead.SetFinishedSlot(bind(&CodedReader::OnDataRead, this),
    Store(readResult));
}

CodedReader::~CodedReader() {}

void CodedReader::Read(void* destination, int size, Out<Async<int> > sizeRead) {
  if(m_sizeRemaining != 0) {
    m_sizeRemaining = max(0, m_sizeRemaining - size);
    m_destinationReader->Read(destination, size, Store(sizeRead));
    return;
  }
  m_result = sizeRead.Get();
  m_buffer = NULL;
  m_destination = destination;
  m_size = size;
  m_sourceBuffer.Reset();
  m_dataRead.ResetResult();
  m_source->Read(&m_sourceBuffer, Store(m_dataRead));
}

void CodedReader::Read(Buffer* destination, int size,
    Out<Async<int> > sizeRead) {
  if(m_sizeRemaining != 0) {
    m_sizeRemaining = max(0, m_sizeRemaining - size);
    m_destinationReader->Read(destination, size, Store(sizeRead));
    return;
  }
  m_result = sizeRead.Get();
  m_buffer = destination;
  m_destination = NULL;
  m_size = size;
  m_sourceBuffer.Reset();
  m_bufferRead.ResetResult();
  m_source->Read(&m_sourceBuffer, Store(m_bufferRead));
}

void CodedReader::OnBufferRead() {
  if(m_bufferRead.GetState() == Async<int>::EXCEPTION) {
    m_sizeRemaining = 0;
    m_result->SetException(m_bufferRead.GetException());
    return;
  }
  try {
    Buffer codedBuffer;
    m_decoder->Decode(m_sourceBuffer, &codedBuffer, Store(m_sizeRemaining));
    m_destinationReader.reset(new BufferReader(codedBuffer));
    m_sizeRemaining = max(0, m_sizeRemaining - m_size);
    m_destinationReader->Read(m_buffer, m_size, Store(*m_result));
  } catch(boost::exception&) {
    m_result->SetException(current_exception());
  } catch(std::exception& e) {
    m_result->SetException(e);
  }
}

void CodedReader::OnDataRead() {
  if(m_dataRead.GetState() == Async<int>::EXCEPTION) {
    m_sizeRemaining = 0;
    m_result->SetException(m_dataRead.GetException());
    return;
  }
  try {
    Buffer codedBuffer;
    m_decoder->Decode(m_sourceBuffer, &codedBuffer, Store(m_sizeRemaining));
    m_destinationReader.reset(new BufferReader(codedBuffer));
    m_sizeRemaining = max(0, m_sizeRemaining - m_size);
    m_destinationReader->Read(m_destination, m_size, Store(*m_result));
  } catch(boost::exception&) {
    m_result->SetException(current_exception());
  } catch(std::exception& e) {
    m_result->SetException(e);
  }
}
